{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Object and Scene Detection using Amazon Rekognition"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This notebook provides a walkthrough of [object detection API](https://docs.aws.amazon.com/rekognition/latest/dg/labels.html) in Amazon Rekognition to identify objects."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import boto3\n",
    "from IPython.display import HTML, display, Image as IImage\n",
    "from PIL import Image, ImageDraw, ImageFont\n",
    "import time\n",
    "import os"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import sagemaker\n",
    "import boto3\n",
    "\n",
    "sagemaker_session = sagemaker.Session()\n",
    "role = sagemaker.get_execution_role()\n",
    "bucket = sagemaker_session.default_bucket()\n",
    "region = boto3.Session().region_name"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "rekognition = boto3.client('rekognition')\n",
    "s3 = boto3.client('s3')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!mkdir -p ./tmp\n",
    "temp_folder = 'tmp/'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Detect Objects in Image"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "imageName = 'content-moderation/media/cars.png'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "display(IImage(url=s3.generate_presigned_url('get_object', Params={'Bucket': bucket, 'Key': imageName})))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Call Rekognition to Detect Objects in the Image\n",
    "https://docs.aws.amazon.com/rekognition/latest/dg/API_DetectLabels.html"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "detectLabelsResponse = rekognition.detect_labels(\n",
    "    Image={\n",
    "        'S3Object': {\n",
    "            'Bucket': bucket,\n",
    "            'Name': imageName,\n",
    "        }\n",
    "    }\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Review the Raw JSON Response from Rekognition\n",
    "Show JSON response returned by Rekognition Labels API (Object Detection).\n",
    "\n",
    "In the JSON response below, you will see Label, detected instances, confidence score and additional information."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "display(detectLabelsResponse)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Show Bounding Boxes Around Recognized Objects\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    " def drawBoundingBoxes (sourceImage, boxes):\n",
    "    # blue, green, red, grey\n",
    "    colors = ((255,255,255),(255,255,255),(76,182,252),(52,194,123))\n",
    "    \n",
    "    # Download image locally\n",
    "    imageLocation = temp_folder + os.path.basename(sourceImage)\n",
    "    s3.download_file(bucket, sourceImage, imageLocation)\n",
    "\n",
    "    # Draws BB on Image\n",
    "    bbImage = Image.open(imageLocation)\n",
    "    draw = ImageDraw.Draw(bbImage)\n",
    "    width, height = bbImage.size\n",
    "    col = 0\n",
    "    maxcol = len(colors)\n",
    "    line= 3\n",
    "    for box in boxes:\n",
    "        x1 = int(box[1]['Left'] * width)\n",
    "        y1 = int(box[1]['Top'] * height)\n",
    "        x2 = int(box[1]['Left'] * width + box[1]['Width'] * width)\n",
    "        y2 = int(box[1]['Top'] * height + box[1]['Height']  * height)\n",
    "        \n",
    "        draw.text((x1,y1),box[0],colors[col])\n",
    "        for l in range(line):\n",
    "            draw.rectangle((x1-l,y1-l,x2+l,y2+l),outline=colors[col])\n",
    "        col = (col+1)%maxcol\n",
    "    \n",
    "    imageFormat = \"PNG\"\n",
    "    ext = sourceImage.lower()\n",
    "    if(ext.endswith('jpg') or ext.endswith('jpeg')):\n",
    "        imageFormat = 'JPEG'\n",
    "\n",
    "    bbImage.save(imageLocation,format=imageFormat)\n",
    "\n",
    "    display(bbImage)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "boxes = []\n",
    "objects = detectLabelsResponse['Labels']\n",
    "for obj in objects:\n",
    "    for einstance in obj[\"Instances\"]:\n",
    "        boxes.append ((obj['Name'], einstance['BoundingBox']))\n",
    "    \n",
    "drawBoundingBoxes(imageName, boxes)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Display List of Detected Objects"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "flaggedObjects = [\"Car\"]\n",
    "\n",
    "for label in detectLabelsResponse[\"Labels\"]:\n",
    "    if(label[\"Name\"] in flaggedObjects):\n",
    "        print(\"Detected object:\")\n",
    "        print(\"- {} (Confidence: {})\".format(label[\"Name\"], label[\"Confidence\"]))\n",
    "        print(\"  - Parents: {}\".format(label[\"Parents\"]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Recognize Objects in Video\n",
    " Object recognition in video is an async operation. \n",
    "https://docs.aws.amazon.com/rekognition/latest/dg/API_StartLabelDetection.html. \n",
    "\n",
    "- First we start a label detection job which returns a Job Id.\n",
    "- We can then call `get_label_detection` to get the job status and after job is complete, we can get object metadata.\n",
    "- In production use cases, you would usually use StepFunction or SNS topic to get notified when job is complete."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "videoName = 'content-moderation/media/GrandTour720.mp4'\n",
    "\n",
    "strDetail = 'Objects detected in video<br>=======================================<br>'\n",
    "strOverall = 'Objects in the overall video:<br>=======================================<br>'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "# Show video in a player\n",
    "\n",
    "s3VideoUrl = s3.generate_presigned_url('get_object', Params={'Bucket': bucket, 'Key': videoName})\n",
    "\n",
    "videoTag = \"<video controls='controls' autoplay width='640' height='360' name='Video' src='{0}'></video>\".format(s3VideoUrl)\n",
    "\n",
    "videoui = \"<table><tr><td style='vertical-align: top'>{}</td></tr></table>\".format(videoTag)\n",
    "\n",
    "display(HTML(videoui))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Call Rekognition to Start a Job for Object Detection\n",
    "\n",
    "### Additional (Optional) Request Attributes\n",
    "\n",
    "ClientRequestToken:\n",
    "https://docs.aws.amazon.com/rekognition/latest/dg/API_StartLabelDetection.html#rekognition-StartLabelDetection-request-ClientRequestToken\n",
    "\n",
    "JobTag:\n",
    "https://docs.aws.amazon.com/rekognition/latest/dg/API_StartLabelDetection.html#rekognition-StartLabelDetection-request-JobTag\n",
    "\n",
    "MinConfidence:\n",
    "https://docs.aws.amazon.com/rekognition/latest/dg/API_StartLabelDetection.html#rekognition-StartLabelDetection-request-MinConfidence\n",
    "\n",
    "NotificationChannel:\n",
    "https://docs.aws.amazon.com/rekognition/latest/dg/API_StartLabelDetection.html#rekognition-StartLabelDetection-request-NotificationChannel\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Start video label recognition job\n",
    "startLabelDetection = rekognition.start_label_detection(\n",
    "    Video={\n",
    "        'S3Object': {\n",
    "            'Bucket': bucket,\n",
    "            'Name': videoName,\n",
    "        }\n",
    "    },\n",
    ")\n",
    "\n",
    "labelsJobId = startLabelDetection['JobId']\n",
    "display(\"Job Id: {0}\".format(labelsJobId))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Wait for Object Detection Job to Complete"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Wait for object detection job to complete\n",
    "# In production use cases, you would usually use StepFunction or SNS topic to get notified when job is complete.\n",
    "getObjectDetection = rekognition.get_label_detection(\n",
    "    JobId=labelsJobId,\n",
    "    SortBy='TIMESTAMP'\n",
    ")\n",
    "\n",
    "while(getObjectDetection['JobStatus'] == 'IN_PROGRESS'):\n",
    "    time.sleep(5)\n",
    "    print('.', end='')\n",
    " \n",
    "    getObjectDetection = rekognition.get_label_detection(\n",
    "    JobId=labelsJobId,\n",
    "    SortBy='TIMESTAMP')\n",
    "    \n",
    "display(getObjectDetection['JobStatus'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Review Raw JSON Response from Rekognition\n",
    "* Show JSON response returned by Rekognition Object Detection API.\n",
    "* In the JSON response below, you will see list of detected objects and activities.\n",
    "* For each detected object, you will see the `Timestamp` of the frame within the video.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "display(getObjectDetection)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Display Recognized Objects in the Video\n",
    "Display timestamps and objects detected at that time."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "flaggedObjectsInVideo = [\"Car\"]\n",
    "\n",
    "theObjects = {}\n",
    "\n",
    "# Objects detected in each frame\n",
    "for obj in getObjectDetection['Labels']:\n",
    "    ts = obj [\"Timestamp\"]\n",
    "    cconfidence = obj['Label'][\"Confidence\"]\n",
    "    oname = obj['Label'][\"Name\"]\n",
    "    \n",
    "    if(oname in flaggedObjectsInVideo):\n",
    "        print(\"Found flagged object at {} ms: {} (Confidence: {})\".format(ts, oname, round(cconfidence,2)))\n",
    "    \n",
    "    strDetail = strDetail + \"At {} ms: {} (Confidence: {})<br>\".format(ts, oname, round(cconfidence,2))\n",
    "    if oname in theObjects:\n",
    "        cojb = theObjects[oname]\n",
    "        theObjects[oname] = {\"Name\" : oname, \"Count\": 1+cojb[\"Count\"]}\n",
    "    else:\n",
    "        theObjects[oname] = {\"Name\" : oname, \"Count\": 1}\n",
    "\n",
    "# Unique objects detected in video\n",
    "for theObject in theObjects:\n",
    "    strOverall = strOverall + \"Name: {}, Count: {}<br>\".format(theObject, theObjects[theObject][\"Count\"])\n",
    "\n",
    "# Display results\n",
    "display(HTML(strOverall))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "listui = \"<table><tr><td style='vertical-align: top'>{}</td></tr></table>\".format(strDetail)\n",
    "display(HTML(listui))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Worker Safety with Amazon Rekognition\n",
    "You can use Amazon Rekognition to detect if certain objects are not present in the image or video. For example you can perform worker safety audit by revieweing images/video of a construction site  and detecting if there are any workers without safety hat."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "imageName = \"content-moderation/media/hat-detection.png\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "display(IImage(url=s3.generate_presigned_url('get_object', Params={'Bucket': bucket, 'Key': imageName})))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Call Amazon Rekognition to Detect Objects in the Image"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "detectLabelsResponse = rekognition.detect_labels(\n",
    "    Image={\n",
    "        'S3Object': {\n",
    "            'Bucket': bucket,\n",
    "            'Name': imageName,\n",
    "        }\n",
    "    }\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Display Rekognition Response"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "display(detectLabelsResponse)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Show Bounding Boxes Around Recognized Objects\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    " def drawBoundingBoxes (sourceImage, boxes):\n",
    "    # blue, green, red, grey\n",
    "    colors = ((255,255,255),(255,255,255),(76,182,252),(52,194,123))\n",
    "    \n",
    "    # Download image locally\n",
    "    imageLocation = temp_folder + os.path.basename(sourceImage)\n",
    "    s3.download_file(bucket, sourceImage, imageLocation)\n",
    "\n",
    "    # Draws BB on Image\n",
    "    bbImage = Image.open(imageLocation)\n",
    "    draw = ImageDraw.Draw(bbImage)\n",
    "    width, height = bbImage.size\n",
    "    col = 0\n",
    "    maxcol = len(colors)\n",
    "    line= 3\n",
    "    for box in boxes:\n",
    "        x1 = int(box[1]['Left'] * width)\n",
    "        y1 = int(box[1]['Top'] * height)\n",
    "        x2 = int(box[1]['Left'] * width + box[1]['Width'] * width)\n",
    "        y2 = int(box[1]['Top'] * height + box[1]['Height']  * height)\n",
    "        \n",
    "        draw.text((x1,y1),box[0],colors[col])\n",
    "        for l in range(line):\n",
    "            draw.rectangle((x1-l,y1-l,x2+l,y2+l),outline=colors[col])\n",
    "        col = (col+1)%maxcol\n",
    "    \n",
    "    imageFormat = \"PNG\"\n",
    "    ext = sourceImage.lower()\n",
    "    if(ext.endswith('jpg') or ext.endswith('jpeg')):\n",
    "        imageFormat = 'JPEG'\n",
    "\n",
    "    bbImage.save(imageLocation,format=imageFormat)\n",
    "\n",
    "    display(bbImage)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "boxes = []\n",
    "objects = detectLabelsResponse['Labels']\n",
    "for obj in objects:\n",
    "    for einstance in obj[\"Instances\"]:\n",
    "        boxes.append ((obj['Name'], einstance['BoundingBox']))\n",
    "    \n",
    "drawBoundingBoxes(imageName, boxes)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def matchPersonsAndHats(personsList, hardhatsList):\n",
    "\n",
    "    persons = []\n",
    "    hardhats = []\n",
    "    personsWithHats = []\n",
    "\n",
    "    for person in personsList:\n",
    "        persons.append(person)\n",
    "    for hardhat in hardhatsList:\n",
    "        hardhats.append(hardhat)\n",
    "\n",
    "    h = 0\n",
    "    matched = 0\n",
    "    totalHats = len(hardhats)\n",
    "    while(h < totalHats):\n",
    "        hardhat = hardhats[h-matched]\n",
    "        totalPersons = len(persons)\n",
    "        p = 0\n",
    "        while(p < totalPersons):\n",
    "            person = persons[p]\n",
    "            if(not (hardhat['BoundingBoxCoordinates']['x2'] < person['BoundingBoxCoordinates']['x1']\n",
    "                or hardhat['BoundingBoxCoordinates']['x1'] > person['BoundingBoxCoordinates']['x2']\n",
    "                or hardhat['BoundingBoxCoordinates']['y4'] < person['BoundingBoxCoordinates']['y1']\n",
    "                    or hardhat['BoundingBoxCoordinates']['y1'] > person['BoundingBoxCoordinates']['y4']\n",
    "                )):\n",
    "\n",
    "                personsWithHats.append({'Person' : person, 'Hardhat' : hardhat})\n",
    "\n",
    "                del persons[p]\n",
    "                del hardhats[h - matched]\n",
    "\n",
    "                matched = matched + 1\n",
    "\n",
    "                break\n",
    "            p = p + 1\n",
    "        h = h + 1\n",
    "\n",
    "    return (personsWithHats, persons, hardhats)\n",
    "\n",
    "def getBoundingBoxCoordinates(boundingBox, imageWidth, imageHeight):\n",
    "    x1 = 0\n",
    "    y1 = 0\n",
    "    x2 = 0\n",
    "    y2 = 0\n",
    "    x3 = 0\n",
    "    y3 = 0\n",
    "    x4 = 0\n",
    "    y4 = 0\n",
    "\n",
    "    boxWidth = boundingBox['Width']*imageWidth\n",
    "    boxHeight = boundingBox['Height']*imageHeight\n",
    "\n",
    "    x1 = boundingBox['Left']*imageWidth\n",
    "    y1 = boundingBox['Top']*imageWidth\n",
    "\n",
    "    x2 = x1 + boxWidth\n",
    "    y2 = y1\n",
    "\n",
    "    x3 = x2\n",
    "    y3 = y1 + boxHeight\n",
    "\n",
    "    x4 = x1\n",
    "    y4 = y3\n",
    "\n",
    "    return({'x1': x1, 'y1' : y1, 'x2' : x2, 'y2' : y2, 'x3' : x3, 'y3' : y3, 'x4' : x4, 'y4' : y4})\n",
    "\n",
    "def getPersonsAndHardhats(labelsResponse, imageWidth, imageHeight):\n",
    "\n",
    "    persons = []\n",
    "    hardhats = []\n",
    "\n",
    "    for label in labelsResponse['Labels']:\n",
    "        if label['Name'] == 'Person' and 'Instances' in label:\n",
    "            for person in label['Instances']:\n",
    "                    persons.append({'BoundingBox' : person['BoundingBox'], 'BoundingBoxCoordinates' : getBoundingBoxCoordinates(person['BoundingBox'], imageWidth, imageHeight), 'Confidence' : person['Confidence']})\n",
    "        elif ((label['Name'] == 'Hardhat' or label['Name'] == 'Helmet') and 'Instances' in label):\n",
    "            for hardhat in label['Instances']:\n",
    "                hardhats.append({'BoundingBox' : hardhat['BoundingBox'], 'BoundingBoxCoordinates' : getBoundingBoxCoordinates(hardhat['BoundingBox'], imageWidth, imageHeight), 'Confidence' : hardhat['Confidence']})\n",
    "\n",
    "    return (persons, hardhats)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "s3Resource = boto3.resource('s3')\n",
    "bucket = s3Resource.Bucket(bucket)\n",
    "iojb = bucket.Object(imageName)\n",
    "response = iojb.get()\n",
    "file_stream = response['Body']\n",
    "im = Image.open(file_stream)\n",
    "imageWidth, imageHeight = im.size"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "persons, hardhats = getPersonsAndHardhats(detectLabelsResponse, imageWidth, imageHeight)\n",
    "\n",
    "personsWithHats, personsWithoutHats, hatsWihoutPerson = matchPersonsAndHats(persons, hardhats)\n",
    "\n",
    "personsWithHatsCount = len(personsWithHats)\n",
    "personsWithoutHatsCount = len(personsWithoutHats)\n",
    "hatsWihoutPersonCount = len(hatsWihoutPerson)\n",
    "\n",
    "outputMessage = \"Person(s): {}\".format(personsWithHatsCount+personsWithoutHatsCount)\n",
    "outputMessage = outputMessage + \"\\nPerson(s) With Safety Hat: {}\\nPerson(s) Without Safety Hat: {}\".format(personsWithHatsCount, personsWithoutHatsCount)\n",
    "print(outputMessage)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Congratulations!\n",
    "You have successfully used Amazon Rekognition to identify specific objects in images and videos."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# References\n",
    "- https://docs.aws.amazon.com/rekognition/latest/dg/API_DetectLabels.html\n",
    "- https://docs.aws.amazon.com/rekognition/latest/dg/API_StartLabelDetection.html\n",
    "- https://docs.aws.amazon.com/rekognition/latest/dg/API_GetLabelDetection.html"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Release Resources"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%html\n",
    "\n",
    "<p><b>Shutting down your kernel for this notebook to release resources.</b></p>\n",
    "<button class=\"sm-command-button\" data-commandlinker-command=\"kernelmenu:shutdown\" style=\"display:none;\">Shutdown Kernel</button>\n",
    "        \n",
    "<script>\n",
    "try {\n",
    "    els = document.getElementsByClassName(\"sm-command-button\");\n",
    "    els[0].click();\n",
    "}\n",
    "catch(err) {\n",
    "    // NoOp\n",
    "}    \n",
    "</script>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%javascript\n",
    "\n",
    "try {\n",
    "    Jupyter.notebook.save_checkpoint();\n",
    "    Jupyter.notebook.session.delete();\n",
    "}\n",
    "catch(err) {\n",
    "    // NoOp\n",
    "}"
   ]
  }
 ],
 "metadata": {
  "instance_type": "ml.t3.medium",
  "kernelspec": {
   "display_name": "Python 3 (Data Science)",
   "language": "python",
   "name": "python3__SAGEMAKER_INTERNAL__arn:aws:sagemaker:us-east-1:081325390199:image/datascience-1.0"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
